{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-05-17T14:42:42.281391Z",
     "start_time": "2025-05-17T14:42:42.268482Z"
    }
   },
   "source": [
    "import numpy as np\n",
    "\n",
    "\n",
    "class RNN:\n",
    "    def __init__(self, input_size, hidden_size, output_size):\n",
    "        \"\"\"初始化RNN模型\"\"\"\n",
    "        self.Wxh = np.random.randn(input_size, hidden_size) * 0.01  # 输入到隐藏层权重\n",
    "        self.Whh = (\n",
    "            np.random.randn(hidden_size, hidden_size) * 0.01\n",
    "        )  # 隐藏层到隐藏层权重\n",
    "        self.Why = (\n",
    "            np.random.randn(hidden_size, output_size) * 0.01\n",
    "        )  # 隐藏层到输出层权重\n",
    "        self.bh = np.zeros((1, hidden_size))  # 隐藏层偏置\n",
    "        self.by = np.zeros((1, output_size))  # 输出层偏置\n",
    "\n",
    "    def forward(self, xt, ht_1):\n",
    "        \"\"\"前向传播\"\"\"\n",
    "        ht = np.tanh(np.dot(xt, self.Wxh) + np.dot(ht_1, self.Whh) + self.bh)\n",
    "        yt = np.dot(ht, self.Why) + self.by\n",
    "        return ht, yt\n",
    "\n",
    "    def backward(self, x, y, h, learning_rate=0.01):\n",
    "        \"\"\"反向传播\"\"\"\n",
    "        dWxh = np.zeros_like(self.Wxh)\n",
    "        dWhh = np.zeros_like(self.Whh)\n",
    "        dWhy = np.zeros_like(self.Why)\n",
    "        dbh = np.zeros_like(self.bh)\n",
    "        dby = np.zeros_like(self.by)\n",
    "\n",
    "        dh_next = np.zeros_like(h[0])\n",
    "        loss = 0\n",
    "\n",
    "        for t in reversed(range(len(x))):\n",
    "            yt = np.dot(h[t], self.Why) + self.by\n",
    "            dy = yt - y[t]\n",
    "            loss += np.mean(dy**2) / 2\n",
    "\n",
    "            dWhy += np.dot(h[t].T, dy)\n",
    "            dby += dy\n",
    "\n",
    "            dh = np.dot(dy, self.Why.T) + dh_next\n",
    "            dtanh = 1 - h[t] ** 2\n",
    "            dh_raw = dh * dtanh\n",
    "\n",
    "            dWxh += np.dot(x[t].T, dh_raw)\n",
    "            dbh += dh_raw\n",
    "            dWhh += np.dot(h[t - 1].T, dh_raw)\n",
    "\n",
    "            dh_next = np.dot(dh_raw, self.Whh.T)\n",
    "\n",
    "        for dparam in [dWxh, dWhh, dWhy, dbh, dby]:\n",
    "            np.clip(dparam, -5, 5, out=dparam)\n",
    "\n",
    "        self.Wxh -= learning_rate * dWxh\n",
    "        self.Whh -= learning_rate * dWhh\n",
    "        self.Why -= learning_rate * dWhy\n",
    "        self.bh -= learning_rate * dbh\n",
    "        self.by -= learning_rate * dby\n",
    "\n",
    "        return loss\n",
    "\n",
    "    def train(self, x_train, y_train, epochs=100, learning_rate=0.01, batch_size=32):\n",
    "        \"\"\"_summary_\n",
    "        训练RNN模型\n",
    "        Args:\n",
    "            x_train (list): 训练输入序列，形状为 [(num_samples, T, input_size)]\n",
    "            y_train (list): 训练目标序列，形状为 [(num_samples, T, output_size)]\n",
    "            epochs (int): 训练轮数\n",
    "            learning_rate (float): 学习率\n",
    "            batch_size (int): 批次大小\n",
    "        Returns:\n",
    "            list: 每轮的平均损失\n",
    "        \"\"\"\n",
    "        losses = []\n",
    "        num_samples = len(x_train)\n",
    "\n",
    "        for epoch in range(epochs):\n",
    "            epoch_loss = 0\n",
    "            # 随机打乱数据\n",
    "            indices = np.random.permutation(num_samples)\n",
    "\n",
    "            for i in range(0, num_samples, batch_size):\n",
    "                batch_indices = indices[i : min(i + batch_size, num_samples)]\n",
    "\n",
    "                for idx in batch_indices:\n",
    "                    x = x_train[idx]  # (T, input_size)\n",
    "                    y = y_train[idx]  # (T, output_size)\n",
    "\n",
    "                    # 初始化隐藏状态\n",
    "                    h = [np.zeros((1, self.Whh.shape[0]))]\n",
    "\n",
    "                    # 前向传播\n",
    "                    for t in range(len(x)):\n",
    "                        ht, yt = self.forward(x[t], h[-1])\n",
    "                        h.append(ht)\n",
    "\n",
    "                    # 反向传播\n",
    "                    loss = self.backward(x, y, h, learning_rate)\n",
    "                    epoch_loss += loss\n",
    "\n",
    "                # 平均批次损失\n",
    "                epoch_loss /= len(batch_indices)\n",
    "\n",
    "            losses.append(epoch_loss)\n",
    "            if (epoch + 1) % 10 == 0:\n",
    "                print(f\"Epoch {epoch + 1}/{epochs}, Loss: {epoch_loss:.6f}\")\n",
    "\n",
    "        return losses\n",
    "\n",
    "    def evaluate(self, x_test, y_test):\n",
    "        \"\"\"_summary_\n",
    "        评估RNN模型\n",
    "        Args:\n",
    "            x_test (list): 测试输入序列，形状为 [(num_samples, T, input_size)]\n",
    "            y_test (list): 测试目标序列，形状为 [(num_samples, T, output_size)]\n",
    "        Returns:\n",
    "            float: 测试集上的平均MSE损失\n",
    "        \"\"\"\n",
    "        total_loss = 0\n",
    "        num_samples = len(x_test)\n",
    "\n",
    "        for i in range(num_samples):\n",
    "            x = x_test[i]  # (T, input_size)\n",
    "            y = y_test[i]  # (T, output_size)\n",
    "\n",
    "            # 初始化隐藏状态\n",
    "            h = np.zeros((1, self.Whh.shape[0]))\n",
    "            loss = 0\n",
    "\n",
    "            # 前向传播\n",
    "            for t in range(len(x)):\n",
    "                h, yt = self.forward(x[t], h)\n",
    "                dy = yt - y[t]\n",
    "                loss += np.mean(dy**2) / 2\n",
    "\n",
    "            total_loss += loss\n",
    "\n",
    "        return total_loss / num_samples"
   ],
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-17T14:42:47.576091Z",
     "start_time": "2025-05-17T14:42:42.336028Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def generate_sine_wave(num_samples, T, input_size, output_size):\n",
    "    x_data = []\n",
    "    y_data = []\n",
    "    for _ in range(num_samples):\n",
    "        t = np.linspace(0, 10, T)\n",
    "        x = np.sin(t).reshape(T, 1)  # 输入是正弦值\n",
    "        y = np.sin(t + 0.1).reshape(T, 1)  # 输出是稍后的正弦值\n",
    "        x_data.append(x)\n",
    "        y_data.append(y)\n",
    "    return x_data, y_data\n",
    "\n",
    "\n",
    "# 参数\n",
    "input_size = 1\n",
    "hidden_size = 32\n",
    "output_size = 1\n",
    "T = 50\n",
    "num_train = 100\n",
    "num_test = 20\n",
    "\n",
    "# 生成数据\n",
    "x_train, y_train = generate_sine_wave(num_train, T, input_size, output_size)\n",
    "x_test, y_test = generate_sine_wave(num_test, T, input_size, output_size)\n",
    "\n",
    "# 初始化RNN\n",
    "rnn = RNN(input_size, hidden_size, output_size)\n",
    "\n",
    "# 训练\n",
    "losses = rnn.train(x_train, y_train, epochs=50, learning_rate=0.001, batch_size=32)\n",
    "\n",
    "# 评估\n",
    "test_loss = rnn.evaluate(x_test, y_test)\n",
    "print(f\"Test Loss: {test_loss:.6f}\")\n",
    "\n",
    "# 可视化训练损失\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.plot(losses)\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Loss\")\n",
    "plt.title(\"Training Loss\")\n",
    "plt.show()"
   ],
   "id": "a272ef38e7e5abc9",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 10/50, Loss: 0.841604\n",
      "Epoch 20/50, Loss: 0.097263\n",
      "Epoch 30/50, Loss: 0.085608\n",
      "Epoch 40/50, Loss: 0.076568\n",
      "Epoch 50/50, Loss: 0.067389\n",
      "Test Loss: 0.526193\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjMAAAHFCAYAAAAHcXhbAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAA22UlEQVR4nO3deXxU9b3/8fdkYbKQHZIQCEgNGAEJAoqACkihgqKUWlcqdvmpFaxc7FWoC2jVqK1KvVRaqiJetVKrcrm1qChbFbkEwlZQQIUQJSGyZYXJdn5/wBwyJGzJWWbC6/l4zIPMmTNnPvkSydvP+Z7z9RiGYQgAACBEhbldAAAAQEsQZgAAQEgjzAAAgJBGmAEAACGNMAMAAEIaYQYAAIQ0wgwAAAhphBkAABDSCDMAACCkEWaAs4TH4zmtx7Jly1r0OTNmzJDH42nWe5ctW2ZJDS357L///e+OfzaAlolwuwAAzvjss88Cnv/2t7/V0qVLtWTJkoDtPXr0aNHn/OIXv9CVV17ZrPf27dtXn332WYtrAHB2IcwAZ4lLLrkk4Hn79u0VFhbWaPvxqqqqFBMTc9qf06lTJ3Xq1KlZNcbHx5+yHgA4HqeZAJiGDh2qXr16acWKFRo0aJBiYmL0s5/9TJI0f/58jRw5Uh06dFB0dLTOP/98TZ06VZWVlQHHaOo00znnnKOrr75a77//vvr27avo6GhlZ2fr5ZdfDtivqdNMt912m9q2basvv/xSo0ePVtu2bZWZmal7771XPp8v4P3ffPONrrvuOsXFxSkxMVG33HKL8vLy5PF49Morr1gyRv/+97917bXXKikpSVFRUerTp4/mzZsXsE99fb0ee+wxnXfeeYqOjlZiYqJ69+6tP/zhD+Y+3333nW6//XZlZmbK6/Wqffv2Gjx4sD766CNL6gTOJnRmAAQoKirS+PHjdd999+mJJ55QWNiR/+fZvn27Ro8ercmTJys2NlZffPGFnnrqKa1evbrRqaqmbNiwQffee6+mTp2qtLQ0vfjii/r5z3+urKwsXX755Sd9b01Nja655hr9/Oc/17333qsVK1bot7/9rRISEvTwww9LkiorKzVs2DDt379fTz31lLKysvT+++/rhhtuaPmgHLV161YNGjRIqampev7555WSkqLXXntNt912m/bs2aP77rtPkvT0009rxowZevDBB3X55ZerpqZGX3zxhQ4ePGge6yc/+Yny8/P1+OOPq3v37jp48KDy8/O1b98+y+oFzhoGgLPShAkTjNjY2IBtQ4YMMSQZH3/88UnfW19fb9TU1BjLly83JBkbNmwwX5s+fbpx/D8tXbp0MaKiooyCggJz26FDh4zk5GTjjjvuMLctXbrUkGQsXbo0oE5Jxt/+9reAY44ePdo477zzzOd//OMfDUnGokWLAva74447DEnG3LlzT/o9+T/7rbfeOuE+N954o+H1eo1du3YFbB81apQRExNjHDx40DAMw7j66quNPn36nPTz2rZta0yePPmk+wA4PZxmAhAgKSlJV1xxRaPtX3/9tW6++Walp6crPDxckZGRGjJkiCTp888/P+Vx+/Tpo86dO5vPo6Ki1L17dxUUFJzyvR6PR2PGjAnY1rt374D3Ll++XHFxcY0mH990002nPP7pWrJkiYYPH67MzMyA7bfddpuqqqrMSdYXX3yxNmzYoLvuuksffPCBysrKGh3r4osv1iuvvKLHHntMq1atUk1NjWV1AmcbwgyAAB06dGi0raKiQpdddpn+7//+T4899piWLVumvLw8vfPOO5KkQ4cOnfK4KSkpjbZ5vd7Tem9MTIyioqIavffw4cPm83379iktLa3Re5va1lz79u1rcnwyMjLM1yVp2rRp+v3vf69Vq1Zp1KhRSklJ0fDhw7VmzRrzPfPnz9eECRP04osvauDAgUpOTtatt96q4uJiy+oFzhaEGQABmrpHzJIlS7R79269/PLL+sUvfqHLL79c/fv3V1xcnAsVNi0lJUV79uxptN3KcJCSkqKioqJG23fv3i1JateunSQpIiJCU6ZMUX5+vvbv36+//vWvKiws1A9+8ANVVVWZ+86cOVM7d+5UQUGBcnNz9c477+i2226zrF7gbEGYAXBK/oDj9XoDtv/5z392o5wmDRkyROXl5Vq0aFHA9jfffNOyzxg+fLgZ7Bp69dVXFRMT0+Rl5YmJibruuus0ceJE7d+/Xzt37my0T+fOnTVp0iSNGDFC+fn5ltULnC24mgnAKQ0aNEhJSUm68847NX36dEVGRur111/Xhg0b3C7NNGHCBD333HMaP368HnvsMWVlZWnRokX64IMPJMm8KutUVq1a1eT2IUOGaPr06frHP/6hYcOG6eGHH1ZycrJef/11vffee3r66aeVkJAgSRozZox69eql/v37q3379iooKNDMmTPVpUsXdevWTaWlpRo2bJhuvvlmZWdnKy4uTnl5eXr//fc1btw4awYEOIsQZgCcUkpKit577z3de++9Gj9+vGJjY3Xttddq/vz56tu3r9vlSZJiY2O1ZMkSTZ48Wffdd588Ho9GjhypF154QaNHj1ZiYuJpHeeZZ55pcvvSpUs1dOhQrVy5Ur/5zW80ceJEHTp0SOeff77mzp0bcHpo2LBhevvtt/Xiiy+qrKxM6enpGjFihB566CFFRkYqKipKAwYM0H//939r586dqqmpUefOnXX//febl3cDOH0ewzAMt4sAALs88cQTevDBB7Vr165m35kYQHCjMwOg1Zg1a5YkKTs7WzU1NVqyZImef/55jR8/niADtGKEGQCtRkxMjJ577jnt3LlTPp/PPHXz4IMPul0aABtxmgkAAIQ0Ls0GAAAhjTADAABCGmEGAACEtFY/Abi+vl67d+9WXFxck7dpBwAAwccwDJWXlysjI+OUN71s9WFm9+7djVa4BQAAoaGwsPCUt1Zo9WHGvxBeYWGh4uPjXa4GAACcjrKyMmVmZp7WgratPsz4Ty3Fx8cTZgAACDGnM0WECcAAACCkEWYAAEBII8wAAICQRpgBAAAhjTADAABCGmEGAACENMIMAAAIaYQZAAAQ0ggzAAAgpLkaZlasWKExY8YoIyNDHo9HCxYsMF+rqanR/fffrwsuuECxsbHKyMjQrbfeqt27d7tXMAAACDquhpnKykrl5ORo1qxZjV6rqqpSfn6+HnroIeXn5+udd97Rtm3bdM0117hQKQAACFYewzAMt4uQjqy98O6772rs2LEn3CcvL08XX3yxCgoK1Llz59M6bllZmRISElRaWsraTAAAhIgz+f0dUgtNlpaWyuPxKDEx8YT7+Hw++Xw+83lZWZkttVT4anWwqlpRkeFq19Zry2cAAIBTC5kJwIcPH9bUqVN18803nzSh5ebmKiEhwXxkZmbaUs/cT3bo0qeW6pkPt9pyfAAAcHpCIszU1NToxhtvVH19vV544YWT7jtt2jSVlpaaj8LCQltqiooMlyT5auptOT4AADg9QX+aqaamRtdff7127NihJUuWnPK8mdfrlddr/2kfb+SRHHi4ts72zwIAACcW1GHGH2S2b9+upUuXKiUlxe2STN6II2GGzgwAAO5y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E3X77bdr06ZNWrhwobKysgI+45FHHtH111+vjRs3avTo0brlllu0f/9+R79PABaxZLlKADhNEyZMMMLDw43Y2NiAx6OPPmoYxpGVvO+8886A9wwYMMD45S9/aRiGYcyZM8dISkoyKioqzNffe+89IywszCguLjYMwzAyMjKMBx544IQ1SDIefPBB83lFRYXh8XiMRYsWWfZ9AnAOc2YAOG7YsGGaPXt2wLbk5GTz64EDBwa8NnDgQK1fv16S9PnnnysnJ0exsbHm64MHD1Z9fb22bt0qj8ej3bt3a/jw4SetoXfv3ubXsbGxiouLU0lJSXO/JQAuIswAcFxsbGyj0z6n4vF4JEmGYZhfN7VPdHT0aR0vMjKy0Xvr6+vPqCYAwYE5MwCCzqpVqxo9z87OliT16NFD69evV2Vlpfn6p59+qrCwMHXv3l1xcXE655xz9PHHHztaMwD30JkB4Difz6fi4uKAbREREWrXrp0k6a233lL//v116aWX6vXXX9fq1av10ksvSZJuueUWTZ8+XRMmTNCMGTP03Xff6e6779ZPfvITpaWlSZJmzJihO++8U6mpqRo1apTKy8v16aef6u6773b2GwXgCMIMAMe9//776tChQ8C28847T1988YWkI1cavfnmm7rrrruUnp6u119/XT169JAkxcTE6IMPPtA999yjiy66SDExMfrRj36kZ5991jzWhAkTdPjwYT333HP69a9/rXbt2um6665z7hsE4CiPYRiG20UAgJ/H49G7776rsWPHul0KgBDBnBkAABDSCDMAACCkMWcGQFDhzDeAM0VnBgAAhDTCDAAACGmEGQAAENIIMwAAIKQRZgAAQEgjzAAAgJBGmAEAACGNMAMAAEIaYQYAAIS0/w9ldQ8bQs+AIwAAAABJRU5ErkJggg=="
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-17T14:42:47.592985Z",
     "start_time": "2025-05-17T14:42:47.590453Z"
    }
   },
   "cell_type": "code",
   "source": "",
   "id": "b5814107e211e685",
   "outputs": [],
   "execution_count": null
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
